home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Turnbull China Bikeride
/
Turnbull China Bikeride - Disc 1.iso
/
ARGONET
/
PD
/
SOUND
/
REPLAYER.SPK
/
c
/
playback
< prev
next >
Wrap
Text File
|
1998-09-04
|
22KB
|
677 lines
/* playback.c
Replayer -- audio player
Copyright (c) 1997 Mark Seaborn <mseaborn@argonet.co.uk>
Contains the functions for calling the Acorn Replay sound drivers to
produce sound output. This file is not portable unless it is re-written
for different sets of drivers, and is the main non-portable component
of Replayer.
*/
#include <string.h>
#include <time.h>
#include "swis.h"
#include "OS:os.h"
#include "OS:osbyte.h"
#include "OS:osfile.h"
#include "OS:osmodule.h"
#include "OS:osfind.h"
#include "OS:osgbpb.h"
#ifdef MemCheck_MEMCHECK
#include "MemCheck:MemCheck.h"
#endif
#include "replayer.h"
#include "replaydriver.h"
/* Functions for finding information about particular Acorn Replay
sound drivers */
/* Gets information about a type 2 sound driver when given the leafname of
the directory that the driver resides in. You must free the structure it
returns yourself. Returns 0 for failure. */
replayer_sound_driver_info *replayer_get_driver_info_type2(const char *name)
{
/* The variables we need. */
replayer_sound_driver_info *info = 0;
char *filename = 0;
FILE *file = 0;
if(!name) return 0;
/* Allocate the info structure. */
info = malloc(sizeof(replayer_sound_driver_info));
if(!info) goto fail;
/* Open the "Info" file for the driver. */
filename = malloc(strlen(replaydriver_DRIVER_PREFIX) + strlen(name) + 10);
if(!filename) goto fail;
strcpy(filename, replaydriver_DRIVER_PREFIX);
strcat(filename, name);
strcat(filename, ".Info");
file = fopen(filename, "r");
if(!file) goto fail;
/* Read the first two text lines. */
info->name = replayer_read_header_line(file);
info->copyright = replayer_read_header_line(file);
/* Read flag and precision. */
info->start_anywhere = replayer_read_header_number(file);
info->precision = replayer_read_header_number(file);
/* Read the final three specialised values. */
info->variable_ratio = replayer_read_header_number(file);
{
char *line = replayer_read_header_line(file);
info->max_sample_bits = atof(line);
free(line);
}
info->buffer_overhead = replayer_read_header_number(file);
/* Return successfully. */
return info;
/* Return with failure; free up things. */
fail:
free(info);
free(filename);
if(file) fclose(file);
return 0;
}
/* Returns information about the sound driver needed to play the sound in
the soundtrack given. If the soundtrack is type 2, it calls
replayer_get_driver_info_type(). Returns 0 for failure. You must
free everything yourself. */
replayer_sound_driver_info *replayer_get_driver_info_from_soundtrack
(const replayer_soundtrack_info *tinfo)
{
if(!tinfo) return 0;
switch(tinfo->type_number) {
/* Type 1 sound formats. */
case 1: {
replayer_sound_driver_info *info;
const char *name, *copyright;
/* Allocate the info block to return. */
info = malloc(sizeof(replayer_sound_driver_info));
if(!info) return 0;
/* Default values to fill in (might be slightly wrong). */
name = "Type 1, vanilla sound";
copyright = "Type 1 driver, copyright unknown";
info->copyright = malloc(strlen(copyright)+1);
if(info->copyright) strcpy(info->copyright, copyright);
info->precision = 8;
info->start_anywhere = 1;
info->variable_ratio = 0;
info->max_sample_bits = 0; /* Unknown. */
info->buffer_overhead = 0; /* N/A. */
/* Alter the defaults for ADPCM sound. */
if((tinfo->precision != 16 && !tinfo->linear_sound) &&
(tinfo->precision == 4 || tinfo->adpcm_sound)) {
name = "Type 1, ADPCM sound";
info->precision = 16;
info->start_anywhere = 0;
info->variable_ratio = 1;
}
/* Copy the name across and return. */
info->name = malloc(strlen(name)+1);
if(info->name) strcpy(info->name, name);
return info; }
/* Type 2: could be anything else. */
case 2:
return replayer_get_driver_info_type2(tinfo->type);
/* Something we don't recognise; just fail. */
default:
return 0;
}
}
/* Frees up a driver info structure and all the values in it. */
void replayer_free_driver_info(replayer_sound_driver_info *info)
{
if(!info) return;
free(info->name);
free(info->copyright);
free(info);
}
/* Methods for finding the filename of the driver you want */
/* This method returns the filename of the driver file which contains the
code necessary for playing this particular Replay soundtrack. You must
free the string it returns yourself. It will return zero if it fails. */
char *replayer_get_driver_filename(const replayer_soundtrack_info *info)
{
/* Variables we need. */
char *filename;
if(!info) return 0;
/* Allocate the filename and copy the initial prefix. */
filename = malloc(strlen(replaydriver_DRIVER_PREFIX) + 24);
if(!filename) return 0;
strcpy(filename, replaydriver_DRIVER_PREFIX);
switch(info->type_number) {
/* Type 1 sound format. */
case 1:
strcat(filename, "Sound");
if(info->precision == 16 || info->linear_sound) {
if(info->unsigned_sound)
strcat(filename, "U");
else strcat(filename, "S");
}
else {
if(info->precision == 4 || info->adpcm_sound)
strcat(filename, "A");
else strcat(filename, "E");
}
sprintf(filename + strlen(filename), "%i", info->precision);
break;
/* Type 2: could be anything else. */
case 2: {
/* Finish off the driver's filename. The leafname of the driver's
directory has already been extracted for us! */
strcat(filename, info->type);
strcat(filename, ".Play");
break; }
/* We don't recognise the sound type. */
default:
free(filename);
return 0;
}
/* Add the number of channels to the filename. */
if(info->channels > 1)
sprintf(filename + strlen(filename), "x%i", info->channels);
/* And return our filename... */
return filename;
}
/* This method just calls replayer_get_driver_filename() but takes a
Replayer file and a soundtrack number, and validates that the track
exists first. Will return zero if it fails. */
char *replayer_obj_get_driver_filename(const replayer_file *obj, int track)
{
const replayer_header *header;
if(!obj) return 0;
header = replayer_get_header(obj);
if(!header) return 0;
if(track >= header->no_soundtracks) return 0;
else return replayer_get_driver_filename(&header->soundtracks[track]);
}
/* Public functions for sound playback */
/* Starts sound-only playback on the Replay file. Returns non-zero for
failure (see header file for reason codes). You must tell it which
soundtrack in the file to use (this will usually be 0). */
int replayer_sound_play(replayer_file *obj, int track)
{
replayer_sound_driver_info *driver_info = 0;
replayer_soundtrack_info *info;
replaydriver_timecheck_state time_check;
int rc = -1; /* Return reason code */
if(!obj) return -1; /* Failure */
/* Find the driver needed and load it, if it's not already loaded. */
if(!obj->driver) {
char *driver_filename = replayer_obj_get_driver_filename(obj, track);
if(!driver_filename) { rc = replayer_play_FORMAT_ERROR; goto fail; }
obj->driver = replaydriver_load(driver_filename);
free(driver_filename);
if(!obj->driver) { rc = replayer_play_DRIVER_ERROR; goto fail; }
}
/* Allocate a control block if it hasn't been allocated already. */
if(!obj->control) {
obj->control = replaydriver_control_create();
if(!obj->control) { rc = replayer_play_ALLOC_ERROR; goto fail; }
}
/* We know the soundtrack number is okay now and the header is there. */
info = &obj->header->soundtracks[track];
/* If playing backwards, ensure that it can be done with this
sound type. */
#ifndef replayer_NO_BACKWARDS
if(obj->backwards) {
if(info->type_number != 1 ||
(info->precision != 8 && info->precision != 16) ||
info->adpcm_sound) { rc = replayer_play_FORMAT_ERROR; goto fail; }
}
#endif
/* Fill in the control block. */
obj->control->data.flags = 0;
obj->control->data.skip_late_data = obj->skip_late_data;
replaydriver_set_rate(obj->control, info->rate);
obj->control->data.quality = obj->quality;
obj->control->data.reversed = info->reverse_channels;
/* Start the timing check. */
replaydriver_timecheck_start(obj->driver, obj->control, &time_check);
/* We are now technically playing. */
#ifndef replayer_NO_BACKWARDS
if(obj->backwards) obj->current_chunk = obj->header->no_chunks;
else /* Drop through */
#endif
obj->current_chunk = 0;
obj->track_playing = track;
obj->playing = 1;
/* Get the information about the driver. We need to know the number of
sound storage bits for later on. */
driver_info = replayer_get_driver_info_from_soundtrack(info);
if(!driver_info)
{ rc = replayer_play_DRIVER_ERROR; goto fail_in_soundtest; }
/* Allocate space for the sound buffers. */
if(!obj->buffer) {
/* Find the amount of storage needed for each buffer. */
size_t buffer_size = ((size_t)
((double) obj->header->frames_per_chunk /
(double) obj->header->fps /* Find seconds per chunk. */
* info->rate /* Account for sample rate. */
* info->channels /* Account for number of channels. */
* 1.01 /* Add 1% extra space. */
* driver_info->precision /* Number of bits per sample. */
+ 7) / 8 /* Round up and convert bits -> bytes. */
+ 12 + 4 /* Add some more. */
+ 3) & ~3; /* Word align it. */
obj->buffer = replaydriver_buffer_create(buffer_size, obj->driver);
if(!obj->buffer)
{ rc = replayer_play_ALLOC_ERROR; goto fail_in_soundtest; }
}
/* Presumably the driver uses up buffer 0 first. */
obj->next_buffer = 0;
#if 0
/* Allocate space for the buffers. */
total_size = (buffer_size + 4) * 2;
{
int os_version;
os_byte(129, 0, 0xFF, &os_version, 0);
if(os_version >= 0xA5) {
/* Running under RISC OS 3.5+, so use a dynamic area. */
if(xosdynamicarea_create(-1, total_size, (byte *) -1, 1<<7,
total_size * 4 /* Max size */,
0, 0, DYNAMIC_AREA_NAME,
&obj->sound_buffer_dynarea, (byte **) &buffers, 0))
goto use_rma_buffer;
}
else {
/* Dynamic areas not available, so allocate from module area. */
use_rma_buffer:
if(xosmodule_alloc(total_size, (void **) &buffers) || !buffers)
goto fail_in_soundtest;
obj->sound_buffer_dynarea = -1;
}
}
obj->sound_buffer = buffers;
obj->sound_buffer_size = buffer_size;
/* Register the buffers with MemCheck. */
#ifdef MemCheck_MEMCHECK
MemCheck_RegisterMiscBlock(buffers, total_size);
#endif
buffer1 = buffers;
buffer2 = buffers + buffer_size + 4;
/* Tell the driver where the buffer is. */
replaydriver_set_buffer(obj->driver, 0, buffer1);
replaydriver_set_buffer(obj->driver, 1, buffer2);
/* Set both buffers to unfilled. */
((int *) buffer1)[1] = 1;
((int *) buffer2)[1] = 1;
/* Put the check word at the end of both buffers. */
*((int *) (buffer1 + buffer_size)) = BUFFER_CHECK_WORD;
*((int *) (buffer2 + buffer_size)) = BUFFER_CHECK_WORD;
}
#endif
/* The sound driver info can now be freed. */
replayer_free_driver_info(driver_info);
driver_info = 0;
/* Open the Replay file so we can slurp in data from it. */
/* We use the OS calls rather than stdio because stdio's buffering may
get in the way and slow things down. We use _swix rather than OSLib
because OSLib's os_f type is only byte-sized, which will break things
when filing systems use file handles >255. */
if(_swix(OS_Find, _IN(0)|_IN(1)|_OUT(0), 0x40, obj->filename, &obj->file)
|| !obj->file)
{ rc = replayer_play_FILE_ERROR; goto fail_in_soundtest; }
/* Wait until the timing check is finished if necessary. */
replaydriver_timecheck_finish(obj->driver, obj->control, &time_check);
/* Let the playback commence. */
replaydriver_play(obj->driver, obj->control);
return 0; /* Return successfully. */
fail_in_soundtest:
/* If things fail during the sound timing test, stop the test first. */
replaydriver_timecheck_abort(obj->driver, obj->control, &time_check);
obj->playing = 0;
fail:
/* If things fail, free only the things internal to this function. */
replayer_free_driver_info(driver_info);
return rc;
}
/* Feeds the sound driver with data, filling its buffer if necessary.
Return values are:
<0 -- something was done successfully
==0 -- nothing needed to be done
>0 -- something was tried but went wrong
*/
int replayer_sound_feed(replayer_file *obj)
{
if(!obj || !obj->playing) return 1;
/* Have we run out of chunks already? */
if(obj->current_chunk > obj->header->no_chunks ||
obj->current_chunk < 0) {
/* If the buffers are empty, the playback is finished. */
if(replaydriver_buffers_empty(obj->driver)) {
replayer_sound_stop(obj);
return replayer_feed_FINISHED;
}
/* Otherwise, let it finish. */
else return 0;
}
/* Don't bother if no buffers need filling. */
if(replaydriver_is_hungry(obj->driver)) {
/* The variables we need. */
char *data;
int size; /* Size of chunk and buffer to allocate. */
replayer_chunk_entry *cchunk;
int offset, bytes_not_read, i;
int return_code;
int next_buffer; /* The buffer that will become empty after this. */
/* Find the chunk and allocate the necessary space. */
cchunk = (replayer_chunk_entry *) ((char *) obj->chunks +
replayer_chunk_entry_SIZE(obj->header->no_soundtracks) *
obj->current_chunk);
size = cchunk->sound_size[obj->track_playing];
data = replaydriver_temp_alloc(obj->buffer, size);
if(!data) return replayer_feed_ALLOC_ERROR;
/* Work out the offset and load the data. */
offset = cchunk->offset + cchunk->video_size;
for(i = 0; i < obj->track_playing; ++i) offset += cchunk->sound_size[i];
if(_swix(OS_GBPB, _IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_OUT(3),
3, obj->file, data, cchunk->sound_size[obj->track_playing],
offset, &bytes_not_read) || bytes_not_read != 0) {
/* Failed to read file correctly; return after freeing memory. */
return_code = replayer_feed_FILE_ERROR;
goto finish;
}
/* If we're playing backwards, reverse the data. */
#ifndef replayer_NO_BACKWARDS
if(obj->backwards) {
int sample_size; /* Size of each sample in bytes. */
char *buffer; /* Buffer for swapping samples. */
char *bottom, *top; /* Samples to swap. */
replayer_soundtrack_info *info;
info = &obj->header->soundtracks[obj->track_playing];
sample_size = info->precision * info->channels / 8;
buffer = malloc(sample_size);
if(!buffer) { return_code = replayer_feed_ALLOC_ERROR; goto finish; }
bottom = data;
/* size should be a multiple of sample_size, but just in case: */
top = data + size - (size % sample_size) - sample_size;
/* Swap each pair of samples in turn. */
while(top > bottom) {
memcpy(buffer, top, sample_size); /* temp = top */
memcpy(top, bottom, sample_size); /* top = bottom */
memcpy(bottom, buffer, sample_size); /* bottom = temp */
/* Move onto next pair of samples. */
bottom += sample_size;
top -= sample_size;
}
}
#endif
/* Which buffer will become empty next? The one that's full now
(they can't both be full). */
if(!*replaydriver_buffer_empty(obj->driver, 0)) next_buffer = 0;
else if(!*replaydriver_buffer_empty(obj->driver, 1)) next_buffer = 1;
else next_buffer = -1; /* Neither are full. */
/* Fill the buffer. */
replaydriver_feed(obj->driver, obj->control, data,
cchunk->sound_size[obj->track_playing]);
if(next_buffer == -1) {
/* Try again, which buffer will become empty next? The one that has
just become full (only one must be full). */
if(!*replaydriver_buffer_empty(obj->driver, 0)) next_buffer = 0;
else if(!*replaydriver_buffer_empty(obj->driver, 1)) next_buffer = 1;
}
obj->next_buffer = next_buffer;
/* Check whether the buffer has been overrun. */
/* Replay is braindead in this respect, as there is no way of telling
it what buffer size you calculated for it to check. Of course,
MemCheck can't check up on what the driver is doing. */
if(replaydriver_buffer_check(obj->buffer)) {
fprintf(stderr,
"The check word in the sound buffer has been overwritten (buffer\n"
"was probably not big enough), so something important may have\n"
"been corrupted. Now would be a good time to panic! (But don't\n"
"worry, it's not your fault.)\n");
/* Return after freeing memory. */
return_code = replayer_feed_FATAL;
goto finish;
}
/* Increment chunk counter, and drop through to finish. */
#ifndef replayer_NO_BACKWARDS
if(obj->backwards) --obj->current_chunk;
else /* Drop through */
#endif
++obj->current_chunk;
return_code = replayer_feed_SUCCESS; /* Buffer filled successfully. */
/* Free the data block and increment the chunk counter. */
finish:
replaydriver_temp_free(obj->buffer, data);
return return_code;
}
return 0; /* Nothing was done. */
}
/* Returns a pointer to an integer which will become non-zero when a buffer
needs filling (this pointer will become invalid after playback is
stopped). Used for pollword-nonzero handlers, and the TaskWindow's
equivalent. Returns 0 if the facility doesn't work, or for failure. */
const int *replayer_get_poll_word(replayer_file *obj)
{
if(!obj || !obj->playing) return 0;
return replaydriver_buffer_empty(obj->driver, obj->next_buffer);
}
/* Preferences for playing sound. */
/* Set the sound quality, a value from 1--4 (higher is better quality).
Must be set before playback starts. Default is maximum quality (4). */
void replayer_set_sound_quality(replayer_file *obj, int quality /* 1--4 */)
{
if(!obj) return;
obj->quality = quality;
}
/* Tell the sound player whether to skip late data. This must be set before
playback starts (if it is playing, it won't have an immediate effect).
Default is not to skip data. */
void replayer_set_skip_late_data(replayer_file *obj, bool skip)
{
if(!obj) return;
obj->skip_late_data = skip;
}
/* Methods for inflight entertainment. */
/* Pauses or unpauses the sound. Returns non-zero if unsuccessful. */
int replayer_sound_set_pause(replayer_file *obj, bool flag)
{
if(!obj || !obj->playing) return 1;
if(flag) obj->control->data.flags |= replaydriver_PAUSE;
else obj->control->data.flags &=~ replaydriver_PAUSE;
return 0;
}
/* Returns true if playback is paused. */
bool replayer_sound_get_pause(const replayer_file *obj)
{
if(!obj || !obj->playing) return 0;
return obj->control->data.flags & replaydriver_PAUSE ? 1:0;
}
/* Mutes or un-mutes the sound. Returns non-zero if unsuccessful. */
int replayer_sound_set_mute(replayer_file *obj, bool flag)
{
if(!obj || !obj->playing) return 1;
if(flag) obj->control->data.flags |= replaydriver_MUTE;
else obj->control->data.flags &=~ replaydriver_MUTE;
return 0;
}
/* Returns true if playback is muted. */
bool replayer_sound_get_mute(const replayer_file *obj)
{
if(!obj || !obj->playing) return 0;
return obj->control->data.flags & replaydriver_MUTE ? 1:0;
}
/* Returns the current time position of sound playing, in centiseconds since
playing started. Gives -1 for an error. */
int replayer_sound_get_time(const replayer_file *obj)
{
if(!obj || !obj->playing) return -1;
return replaydriver_get_time(obj->driver, obj->control);
}
/* Returns the total length in time of the Replay file in centiseconds.
Returns -1 for error. */
int replayer_get_length(const replayer_file *obj)
{
const replayer_header *header;
const replayer_chunk_entry *chunk;
const replayer_soundtrack_info *info;
const int track = 0;
int i, total_size = 0;
if(!obj) return -1; /* Fail. */
/* This is done by going through each chunk and converting its size into
a time. Track 0 is used (and why not?). */
header = replayer_get_header(obj);
if(!header || header->no_soundtracks < 1) return -1; /* Fail. */
chunk = replayer_get_chunk_catalogue(obj);
if(!chunk) return -1; /* Fail. */
info = &header->soundtracks[track];
/* Go through each chunk in turn. */
for(i = header->no_chunks; i; --i) {
total_size += chunk->sound_size[track];
/* Next chunk! */
chunk = (replayer_chunk_entry *) ((char *) chunk +
replayer_chunk_entry_SIZE(header->no_soundtracks));
}
/* Convert size to time and return. */
return (int) ((double) total_size /
/* Bytes per centisecond: */
(info->rate * info->precision * info->channels / 8 / 100));
}
#ifndef replayer_NO_BACKWARDS
/* Sets the replayer object to play sound backwards. Returns non-zero for
failure. Cannot be changed while sound is playing. */
int replayer_set_backwards(replayer_file *obj, bool flag)
{
if(!obj || obj->playing) return 1; /* Fail */
obj->backwards = flag;
return 0; /* Success */
}
/* Returns true if the object is set to play sound backwards. */
bool replayer_get_backwards(const replayer_file *obj)
{
return obj ? obj->backwards : 0;
}
#endif /* !defined replayer_NO_BACKWARDS */
/* Finalisation. */
/* Stops playback of sound, but does not call replayer_sound_tidy(), just
in case you want to resume playback again. */
void replayer_sound_stop(replayer_file *obj)
{
if(!obj || !obj->playing) return;
replaydriver_stop(obj->driver, obj->control);
obj->playing = 0;
}
/* Frees up any resources used by sound-only playing. This is not called
when playback is stopped, but it will stop playback as a result of it
being called. */
void replayer_sound_tidy(replayer_file *obj)
{
if(!obj) return;
replayer_sound_stop(obj);
/* Free the space used by the sound driver. */
if(obj->driver) {
replaydriver_destroy(obj->driver);
obj->driver = 0;
}
/* Free the sound control block. */
if(obj->control) {
replaydriver_control_destroy(obj->control);
obj->control = 0;
}
/* Free the sound data buffer. */
if(obj->buffer) {
replaydriver_buffer_destroy(obj->buffer);
obj->buffer = 0;
}
/* Close the input file. */
if(obj->file) {
_swix(OS_Find, _IN(0)|_IN(1), 0, obj->file);
obj->file = 0;
}
}